package com.xjtu.netty;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSONObject;
import com.xjtu.common.constant.Chat;
import com.xjtu.repository.ChatRepository;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;


/**
 * 专门处理文本消息的handler
 */
@Component
@Slf4j
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    private static final ChatRepository chatRepository;

    //记录所有客户端通道
    public final static ChannelGroup CHANNEL_GROUP = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    //记录用户到通道的映射
    private final static ConcurrentHashMap<String, ChannelId> USER_CHANNEL_MAP = new ConcurrentHashMap<>();
    //记录在线的IP
    public final static CopyOnWriteArraySet<String> IP_LIST = new CopyOnWriteArraySet<>();


    static {
        chatRepository = SpringUtil.getBean(ChatRepository.class);
    }

    //客户端携带userId连接，进入聊天室
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        String ip = ctx.channel().remoteAddress().toString().split(":")[0].substring(1);
        //添加到通道组
        CHANNEL_GROUP.add(ctx.channel());
        //添加到IP列表
        IP_LIST.add(ip);

        //TODO: 向redis缓存请求最近的消息并返回前端
    }


    //客户端发送消息
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
        //获取客户端连接的通道
        //Channel channel = ctx.channel();
        //获取客户端传输的消息
        String message = msg.text();

        JSONObject data = JSONObject.parseObject(message);
        String type = data.getString("type");
        JSONObject body = data.getJSONObject("body");
        switch (type) {
            case "online":
                String userId = body.getString("userId");
                //创建时已加到通道组，此处只需加到用户通道映射
                USER_CHANNEL_MAP.put(userId, ctx.channel().id());
                break;
            //
            // case "ping":
            //     Channel channel = ctx.channel();
            //     channel.writeAndFlush(msg);
            //     break;

            case "chat":
                String mode = body.getString("mode");
                //将客户端消息解析为Chat对象
                com.xjtu.model.Chat chat = JSONObject.toJavaObject(body, com.xjtu.model.Chat.class);
                System.out.println(chat.toString());

                if (mode.equals("single")) {
                    //单聊模式
                    //TODO: 直接保存到redis实现已读回执

                    //直接保存到单人聊天集合实现持久化
                    chatRepository.save(chat);

                    //接收方是否在线
                    String to = chat.getTo();
                    boolean online = USER_CHANNEL_MAP.containsKey(to);
                    if (online) {
                        //寻找通道并发送消息
                        Channel toChannel = CHANNEL_GROUP.find(USER_CHANNEL_MAP.get(to));
                        //发两次会出错
                        toChannel.writeAndFlush(new TextWebSocketFrame(message));
                    }
                } else {
                    //群聊模式
                    chatRepository.save(chat, Chat.GROUP_CHAT_COLLECTION);

                    //TODO: 根据群组id查找群成员
                    String groupId = chat.getTo();
                    String[] idList = new String[5];

                    for (String id : idList) {
                        //判断是否在线
                        boolean online = USER_CHANNEL_MAP.containsKey(id);
                        if (online) {
                            //寻找通道并发送消息
                            Channel toChannel = CHANNEL_GROUP.find(USER_CHANNEL_MAP.get(id));
                            //发两次会出错
                            toChannel.writeAndFlush(new TextWebSocketFrame(message));
                        }
                    }
                }
                break;

            case "offline":
                userId = body.getString("userId");
                //移出通道组
                CHANNEL_GROUP.remove(ctx.channel());
                //移出IP列表
                String ip = ctx.channel().remoteAddress().toString().split(":")[0].substring(1);
                IP_LIST.remove(ip);
                //移出用户通道映射
                USER_CHANNEL_MAP.remove(userId);
                break;
        }
    }


    //客户端断开，离开聊天室
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        //移出通道组
        CHANNEL_GROUP.remove(ctx.channel());
        //移出IP列表
        String ip = ctx.channel().remoteAddress().toString().split(":")[0].substring(1);
        IP_LIST.remove(ip);
        System.out.println(ip + "已下线！");
        //移出用户通道映射
        for (Map.Entry<String, ChannelId> entry : USER_CHANNEL_MAP.entrySet()) {
            String userId = entry.getKey();
            if (entry.getValue().equals(ctx.channel().id())) {
                USER_CHANNEL_MAP.remove(userId);
            }
        }
        //TODO: 需要关闭通道
    }


    //消息处理异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        //移出通道组
        CHANNEL_GROUP.remove(ctx.channel());
        //移出IP列表
        String ip = ctx.channel().remoteAddress().toString().split(":")[0].substring(1);
        IP_LIST.remove(ip);
        System.out.println(ip + "出错下线！");
        log.error(cause.toString());
        //移除用户通道映射
        for (Map.Entry<String, ChannelId> entry : USER_CHANNEL_MAP.entrySet()) {
            String userId = entry.getKey();
            if (entry.getValue().equals(ctx.channel().id())) {
                USER_CHANNEL_MAP.remove(userId);
            }
        }
        //关闭通道
        ctx.channel().close();
    }


    /**
     * 获取在线用户列表
     */
    public static List<String> getOnlineList() {
        return new ArrayList<>(USER_CHANNEL_MAP.keySet());
    }
}
